import { Matrix4 } from "../../utils/Matrix4";
import { Primitives } from "../../utils/Primitives";
import { TextrueUtil } from "../../utils/TextureUtil";
import { Util } from "../../utils/Util";
import { WebGLUtil } from "../../utils/WebGLUtil";
import { Render } from "../Render";
import { WebglTemplateBase } from "./WebglTemplateBase";

/*
 * @Author: Snow
 * @Date: 2021-10-13 09:53:20
 * @Description:  投影
 */
export class TemplateProjection extends WebglTemplateBase {

    private cameraProgramInfo: {
        program: WebGLProgram,
        uniformSetters: { [name: string]: Function },
        attribSetters: { [name: string]: Function },
    };

    protected override initUITools(): void {
        const gl = Render.GL;
        let translation = [200, 150];
        let scale = [1, 1];
        this.uiParam = {
            cameraX: 2.75,
            cameraY: 5,
            posX: 3.5,
            posY: 4.4,
            posZ: 4.7,
            targetX: 0.8,
            targetY: 0,
            targetZ: 4.7,
            projWidth: 1,
            projHeight: 1,
        }
        // Setup a ui.
        this.createSlider("cameraX", { value: this.uiParam.cameraX, slide: this.onUpateSlide(this, "cameraX"), min: -10, max: 10, step: 0.1, precision: 1 })
        this.createSlider("cameraY", { value: this.uiParam.cameraY, slide: this.onUpateSlide(this, "cameraY"), min: 1, max: 20, step: 0.1, precision: 1 })
        this.createSlider("posX", { value: this.uiParam.posX, slide: this.onUpateSlide(this, "posX"), min: -10, max: 10, step: 0.03, precision: 2 })
        this.createSlider("posY", { value: this.uiParam.posY, slide: this.onUpateSlide(this, "posY"), min: 1, max: 20, step: 0.1, precision: 1 })
        this.createSlider("posZ", { value: this.uiParam.posZ, slide: this.onUpateSlide(this, "posZ"), min: 1, max: 20, step: 0.1, precision: 1 })
        this.createSlider("targetX", { value: this.uiParam.targetX, slide: this.onUpateSlide(this, "targetX"), min: -10, max: 10, step: 0.1, precision: 1 })
        this.createSlider("targetY", { value: this.uiParam.targetY, slide: this.onUpateSlide(this, "targetY"), min: 0, max: 20, step: 0.1, precision: 1 })
        this.createSlider("targetZ", { value: this.uiParam.targetZ, slide: this.onUpateSlide(this, "targetZ"), min: -10, max: 20, step: 0.1, precision: 1 })
        this.createSlider("projWidth", { value: this.uiParam.projWidth, slide: this.onUpateSlide(this, "projWidth"), min: 1, max: 10, step: 0.1, precision: 1 })
        this.createSlider("projHeight", { value: this.uiParam.projHeight, slide: this.onUpateSlide(this, "projHeight"), min: 1, max: 10, step: 0.1, precision: 1 })
    }

    protected override initProgram(): void {
        this.vsSource = `
            // 顶点着色器
            attribute vec4 a_position;
            attribute vec2 a_texcoord;
            
            uniform mat4 u_projection;
            uniform mat4 u_view;
            uniform mat4 u_world;
            uniform mat4 u_textureMatrix;
            
            varying vec2 v_texcoord;
            varying vec4 v_projectedTexcoord;
            
            void main() {
                vec4 worldPosition = u_world * a_position;
                
                gl_Position = u_projection * u_view * worldPosition;
                
                // 把纹理坐标传给片段着色器
                v_texcoord = a_texcoord;

                v_projectedTexcoord = u_textureMatrix * worldPosition;
            }
        `;

        this.fsSource = `
            // 片段着色器
            precision mediump float;
            
            // 从顶点着色器传来的
            varying vec2 v_texcoord;
            varying vec4 v_projectedTexcoord;
            
            uniform vec4 u_colorMult;
            uniform sampler2D u_texture;
            uniform sampler2D u_projectedTexture;
            
            void main() {
                // 除以 w 得到正确的值，详见透视投影的文章
                vec3 projectedTexcoord = v_projectedTexcoord.xyz / v_projectedTexcoord.w;
                bool inRange = 
                    projectedTexcoord.x >= 0.0 &&
                    projectedTexcoord.x <= 1.0 &&
                    projectedTexcoord.y >= 0.0 &&
                    projectedTexcoord.y <= 1.0;
                
                vec4 projectedTexColor = texture2D(u_projectedTexture, projectedTexcoord.xy);
                vec4 texColor = texture2D(u_texture, v_texcoord) * u_colorMult;

                float projectedAmount = inRange ? 1.0 : 0.0;
                gl_FragColor = mix(texColor, projectedTexColor, projectedAmount);
                //gl_FragColor = texture2D(u_texture, v_texcoord) * u_colorMult;
            }
        `;
        super.initProgram();
        /* 相机顶点 */
        const cameraVsSource = `
            attribute vec4 a_position;
    
            uniform mat4 u_projection;
            uniform mat4 u_view;
            uniform mat4 u_world;
            
            void main() {
                // 将 position 乘以矩阵
                gl_Position = u_projection * u_view * u_world * a_position;
            }
        `;

        const cameraFsSource = `
            precision mediump float;
    
            uniform vec4 u_color;
            
            void main() {
                gl_FragColor = u_color;
            }
        `;
        this.cameraProgramInfo = WebGLUtil.createProgramInfo(Render.GL, cameraVsSource, cameraFsSource);
    }

    protected override initBuffers(): void {
        super.initBuffers();
        const gl = Render.GL;
        gl.enable(gl.CULL_FACE);

        // creates buffers with position, normal, texcoord, and vertex color
        // data for primitives by calling gl.createBuffer, gl.bindBuffer,
        // and gl.bufferData
        //const sphereBufferInfo = Primitives.createSphereWithVertexColorsBufferInfo(gl, 10, 12, 6);
        //   const cubeBufferInfo   = Primitives.createCubeWithVertexColorsBufferInfo(gl, 20);
        //   const coneBufferInfo   = Primitives.createTruncatedConeWithVertexColorsBufferInfo(gl, 10, 0, 20, 12, 1, true, false);


        //const sphereBufferInfo = Primitives.createFlattenedFunc(Primitives.createSphereVertices)(gl, 1, 12, 6);
        // const cubeBufferInfo = Primitives.createFlattenedFunc(Primitives.createCubeVertices)(gl, 20);
        // const coneBufferInfo = Primitives.createFlattenedFunc(Primitives.createTruncatedConeVertices)(gl, 10, 0, 20, 12, 1, true, false);

        const sphereBufferInfo = Primitives.createBufferInfoFunc(Primitives.createSphereVertices)(
            gl,
            1,  // 半径
            12, // 横轴细分数
            6,  // 纵轴细分数
        );

        const planeBufferInfo = Primitives.createBufferInfoFunc(Primitives.createPlaneVertices)(
            gl,
            20,  // 宽
            20,  // 高
            1,   // 横轴细分数
            1,   // 纵轴细分数
        );

        const cubeLinesBufferInfo = WebGLUtil.createBufferInfoFromArrays(gl, {
            position: [
                0, 0, -1,
                1, 0, -1,
                0, 1, -1,
                1, 1, -1,
                0, 0, 1,
                1, 0, 1,
                0, 1, 1,
                1, 1, 1,
            ],
            indices: [
                0, 1,
                1, 3,
                3, 2,
                2, 0,

                4, 5,
                5, 7,
                7, 6,
                6, 4,

                0, 4,
                1, 5,
                3, 7,
                2, 6,
            ],
        });

        // 创建一个 8x8 棋盘纹理
        const checkerboardTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, checkerboardTexture);
        gl.texImage2D(
            gl.TEXTURE_2D,
            0,                // mip level
            gl.LUMINANCE,     // internal format
            8,                // width
            8,                // height
            0,                // border
            gl.LUMINANCE,     // format
            gl.UNSIGNED_BYTE, // type
            new Uint8Array([  // data
                0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
                0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
                0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
                0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
                0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
                0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
                0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
                0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
            ]));
        gl.generateMipmap(gl.TEXTURE_2D);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

        const fTextrue = this.loadImageTexture("f-texture.png");

        // 平面 uniforms
        const planeUniforms = {
            u_colorMult: [0.5, 0.5, 1, 1],  // 浅蓝色
            u_texture: checkerboardTexture,
            u_world: Matrix4.translation(0, 0, 0),
        };

        // 球体
        const sphereUniforms = {
            u_colorMult: [1, 0.5, 0.5, 1],  // 粉红色
            u_texture: checkerboardTexture,
            u_world: Matrix4.translation(2, 3, 4),
        };


        //let bufferInfo = WebGLUtil.createBufferInfoFromArrays(gl, arrays);
        let cameraAngleRadians = Util.degToRad(0);
        let fieldOfViewRadians = Util.degToRad(60);
        let cameraHeight = 50;

        let drawScene = () => {
            let time = this.ts;
            time = time * 0.5 + 5;
            WebGLUtil.resizeCanvasToMatchDisplaySize(gl);
            // Clear the canvas AND the depth buffer.
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            // Compute the projection matrix
            let aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
            var projectionMatrix = Matrix4.perspective(fieldOfViewRadians, aspect, 1, 2000);

            // Compute the camera's matrix using look at.
            let cameraY = this.uiParam.cameraY;
            let cameraX = this.uiParam.cameraX;
            let cameraPosition = [cameraX, cameraY, 7];
            let target = [0, 0, 0];
            let up = [0, 1, 0];
            let cameraMatrix = Matrix4.lookAt(cameraPosition, target, up);

            // Make a view matrix from the camera matrix.
            let viewMatrix = Matrix4.invert(cameraMatrix);

            // 设置球体和平面共享的 uniforms
            if (this.uiParam.posX == 0.8) {
                console.log("");
            }

            let textureWorldMatrix = Matrix4.lookAt(
                [this.uiParam.posX, this.uiParam.posY, this.uiParam.posZ],          // position
                [this.uiParam.targetX, this.uiParam.targetY, this.uiParam.targetZ], // target
                [0, 1, 0],                                              // up
            );

            textureWorldMatrix = Matrix4.multiply(textureWorldMatrix, Matrix4.scaling(this.uiParam.projWidth, this.uiParam.projHeight, 1));

            // 使用这个世界矩阵的逆矩阵来创建
            // 一个矩阵，该矩阵会变换其他世界坐标
            // 为相对于这个空间的坐标。
            const textureMatrix = Matrix4.invert(textureWorldMatrix);

            gl.useProgram(this.programInfo.program);

            WebGLUtil.setUniforms(this.programInfo, {
                u_view: viewMatrix,
                u_projection: projectionMatrix,
                u_textureMatrix: textureMatrix,
                u_projectedTexture: fTextrue,
            });

            // Setup all the needed buffers and attributes.
            WebGLUtil.setBuffersAndAttributes(gl, this.programInfo, sphereBufferInfo);

            // Set the uniforms that are the same for all objects.
            WebGLUtil.setUniforms(this.programInfo, sphereUniforms);

            // Draw the geometry.
            //gl.drawArrays(gl.TRIANGLES, 0, sphereBufferInfo.numElements);
            WebGLUtil.drawBufferInfo(gl, sphereBufferInfo);

            ///立方体
            // Setup all the needed buffers and attributes.
            WebGLUtil.setBuffersAndAttributes(gl, this.programInfo, planeBufferInfo);
            // Set the uniforms that are the same for all objects.
            WebGLUtil.setUniforms(this.programInfo, planeUniforms);
            // Draw the geometry.
            WebGLUtil.drawBufferInfo(gl, planeBufferInfo);



            ///画辅助线

            gl.useProgram(this.cameraProgramInfo.program);
          
            WebGLUtil.setBuffersAndAttributes(gl, this.cameraProgramInfo, cubeLinesBufferInfo);

            // 在 Z 轴上缩放该立方体，
            // 以便表示该纹理是被投影到无限远的。
            //因为投影可以投到无限远的地方可以用Z轴做缩放
            const mat = Matrix4.multiply(textureWorldMatrix, Matrix4.scaling(1, 1, 1));
            //const mat = Matrix4.identity();
            // 设置我们计算出来的 unifroms
            WebGLUtil.setUniforms(this.cameraProgramInfo, {
                u_color: [0, 0, 0, 1],
                u_view: viewMatrix,
                u_projection: projectionMatrix,
                u_world: mat,
            });

            // 调用 gl.drawArrays 或者 gl.drawElements
            WebGLUtil.drawBufferInfo(gl, cubeLinesBufferInfo, gl.LINES);
        }
        this.draw = drawScene;
    }

    /* 加载图片 */
    private loadImageTexture(url: string): WebGLTexture {
        const gl = Render.GL;
        // 创建一个纹理
        const texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        // 用一个 1x1 蓝色像素填充该纹理
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
            new Uint8Array([0, 0, 255, 255]));
        // 异步加载一张图片
        const image = new Image();
        image.src = url;
        image.addEventListener('load', function () {
            // 现在图片加载完了，把它拷贝到纹理中
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
            // 假设该纹理的宽高是 2 的整次幂
            gl.generateMipmap(gl.TEXTURE_2D);
        });
        return texture;
    }
}
